#include <iostream>
#include <string>
#include <map>

#include "PClassRegistry.h"

#include "ADomain.h"
#include "AStream.h"
#include "AObjDoc.h"

#include "AAttrList.h"
#include "ABin.h"
#include "ABinViewSetting.h"
#include "AMCBinRef.h"

#include "ATmpCrumb.h"
#include "TmpBreadCrumbs.h"
#include "AMCMobRef.h"

#include "AMacFileLocator.h"
#include "MSMLocator.h"

#include "TrackMan.h"
#include "AParamItem.h"
#include "AParamList.h"
#include "AGraphicEffectAttr.h"

#include "DIDPosition.h"
#include "DIDDescriptor.h"
#include "RGBADescriptor.h"
#include "CDCIDescriptor.h"

#include "ATimecodeClip.h"
#include "AFillerClip.h"
#include "ASourceClip.h"
#include "AParamClip.h"
#include "AEdgecodeClip.h"
#include "ATrackRef.h"
#include "AClip.h"
#include "ATimeWarp.h"
#include "ACaptureMask.h"
#include "AComponent.h"
#include "ASequence.h"

#include "APanVolEffect.h"
#include "ATrackEffect.h"
#include "AComposition.h"

#include "AMediaFileDesc.h"
#include "AMediaDesc.h"

#include "AProject.h"

#include <iostream>
#include <cstdlib> /*for EXIT_SUCCESS/EXIT_FAILURE*/

/*
 * Register all our classes with the class factory so it knows how to
 * instantiate them when they are read from a file.
 * Note that the Avid doesn't seem to store Settings classes in its PClass
 * registry, they probably have a separate one for that.
 *
 * TODO: Put all of libAvid into a namespace of its own, this is just messy.
 */
void registerClasses()
{
	std::cout << "Registering classes..." << std::endl;

	// General objects
	PClassRegistry::registerClass< AAttrList >();

	// Bin-related objects
	PClassRegistry::registerClass< ABin >();
	PClassRegistry::registerClass< AMCBinRef >();
	PClassRegistry::registerClass< AComponent >();
	PClassRegistry::registerClass< AClip >();
	PClassRegistry::registerClass< ATimecodeClip >();
	PClassRegistry::registerClass< AEdgecodeClip >();
	PClassRegistry::registerClass< AComposition >();
	PClassRegistry::registerClass< AFillerClip >();
	PClassRegistry::registerClass< ASourceClip >();
	PClassRegistry::registerClass< AParamClip >();
	PClassRegistry::registerClass< ATrackRef >();
	PClassRegistry::registerClass< ASequence >();
    PClassRegistry::registerClass< ATrackEffect >();
	PClassRegistry::registerClass< APanVolEffect >();
	PClassRegistry::registerClass< ATimeWarp >();
	PClassRegistry::registerClass< ACaptureMask >();
	PClassRegistry::registerClass< AMCMobRef >();
	PClassRegistry::registerClass< TmpBreadCrumbs >();
	PClassRegistry::registerClass< ATmpCrumb >();
	PClassRegistry::registerClass< AMacFileLocator >();
	PClassRegistry::registerClass< MSMLocator >();
	PClassRegistry::registerClass< TrackMan >();
	PClassRegistry::registerClass< AParamItem >();
	PClassRegistry::registerClass< AParamList >();
	PClassRegistry::registerClass< AGraphicEffectAttr >();
	PClassRegistry::registerClass< AMediaDesc >();
	PClassRegistry::registerClass< AMediaFileDesc >();
	PClassRegistry::registerClass< DIDPosition >();
	PClassRegistry::registerClass< AMSBob >();
	PClassRegistry::registerClass< AOrigMobRef >();
	PClassRegistry::registerClass< DIDDescriptor >();
	PClassRegistry::registerClass< CDCIDescriptor >();
	PClassRegistry::registerClass< RGBADescriptor >();

	// Settings (Media composer seems to have a separate registry for those.
	// Only ASetting and ABinViewSetting are in their regular PClass Registry.
	PClassRegistry::registerClass< ASetting >();
/*	PClassRegistry::registerClass< AudioProjectSetting >();
	PClassRegistry::registerClass< AudioSetting >();*/
	PClassRegistry::registerClass< ABinViewSetting >();

	// Project
	PClassRegistry::registerClass< AProject >();

	// Serialization system classes
	PClassRegistry::registerClass< ADomain >();
	PClassRegistry::registerClass< AObjDoc >(); // Note: Unlike us, Media Composer does NOT put AObjDoc into the PClass Registry
}

static void printVersion()
{
	std::cout << "libAvid Test Application" << std::endl << std::endl;
}

/*
 * List the contents of an Avid bin object (class ABin) to stdout. The "bin" 
 * here has nothing to do with "binary", it is basically a folder of media in
 * an Avid project. This is also where edited sequences live.
 */
static void dumpBin( ABin& bin )
{
	std::cout << "Bin contains " << bin._objects.size() << " objects: " << std::endl;

	ABin::ObjectList::const_iterator it = bin._objects.begin();

	for( ; it != bin._objects.end(); ++it )
	{
		AComposition* comp = ( *it )->_composition;

		if( !comp )
			std::cout << "\t- <null>" << std::endl;

		std::cout << "\t- "
		          << comp->getClassName()
		          << " - "
		          << comp->_name
				  << " - Physical Media: "
			      << ( comp->_physicalMedia ? comp->_physicalMedia->getClassName() : "<null>" )
				  << std::endl;
	}
}

/*
 * This is our actual main function. The regular main() just wraps a call to 
 * this in error handling code.
 */
static void mainImp( int argc, char* argv[] )
{
	printVersion();

	// Set up shop
	registerClasses();

	std::cout << "PClass Registry Dump:" << std::endl;

	// For now, dump the classes we know
	PClassRegistry::dump( std::cout );

	std::string fileName;

	// If we don't have a file supplied as an argument, run on a hard-coded file
	if( argc < 2 ) {
		std::cerr << "Syntax: avblib binFile.avb" << std::endl;

		// Assume our test files for debugging
		// fileName = "/Users/xyz/Documents/Avid Projects/Project Name/Your Bin.avb";
        return;
	} else {
		fileName = argv[ 1 ];
	}

	std::cout << "Reading bin file " << fileName << "..." << std::endl;

	// Open the file
	AStream str( fileName );

	// Read the first domain in the file (for bins, there is only one)
	ADomain d;
	d.readFromStream( str );

	// See what is in the domain we just read, for now we only want an AObjDoc
    // FIXME: Use a smart pointer for exception safety
	AObjDoc* objd = dynamic_cast< AObjDoc* >( d._object.get() );

	if( !objd )
		throw "Failed to read an AObjDoc from the file, it's something else!";

	// Now the AObjDoc contains all the objects we were able to deserialize
	// (or as Avid calls it, domain copyin). We can acces the bin object
	// by simply using AObjDoc::getRootObject().

	AObject* binObj = objd->getRootObject();
	TR_TRACE (
           "Root object is of type "
        << binObj->getClassName()
        << " ('"
        << pcidToString( binObj->getClassId() )
        << "')"
    );

	// Make sure we got a bin
	TR_ASSERT( binObj->getClassId() == 'ABIN' );

	ABin* bin = dynamic_cast< ABin* >( binObj );
	TR_CHECK( bin != nullptr );

    // List the contents of that bin
	dumpBin( *bin );
}

// NOTE: We shouldn't read the end tag markers from the classes themselves,
//       this should be done by AObjDoc. That way if a base class can also be
//       written to a stream, it won't mess up everything.

int main( int argc, char* argv[] )
{
	try {
		mainImp( argc, argv );
	} catch( std::exception& e ) {
		std::cerr << "Exception< std::exeption& >: " << e.what() << std::endl;
		return EXIT_FAILURE;
	} catch( const char* e) {
		std::cerr << "Exception< const char* >: " << e << std::endl;
		return EXIT_FAILURE;;
	}

	return EXIT_SUCCESS;
}
